﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="DynamicVariableResolver.cs" company="Collaboris Ltd.">
//   Copyright (c) Collaboris Ltd. All rights Reserved.
// </copyright>
// <summary>
//   Defines the DynamicVariableResolver type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Collaboris.Saf.Engine.DynamicVariables
{
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.Specialized;

    using Collaboris.Utils;

    using Spring.Objects;

    /// <summary>
    /// Stores the values of the variables and respective names
    /// </summary>
    internal class VariablesDictionary : Dictionary<string, object>
    {
        #region Public Methods

        /// <summary>
        /// The convert.
        /// </summary>
        /// <returns>
        /// </returns>
        public NameValueCollection Convert()
        {
            var nvc = new NameValueCollection();

            foreach (string variableName in this.Keys)
            {
                if (this[variableName] != null)
                    nvc.Add(variableName, this[variableName].ToString());
            }

            return nvc;
        }

        #endregion
    }

    /// <summary>
    /// The dynamic variable resolver.
    /// </summary>
    public class DynamicVariableResolver
    {
        #region Constants and Fields

        /// <summary>
        /// The publications.
        /// </summary>
        private Publications publications = new Publications();

        /// <summary>
        /// Contains the publishing variables and respective values
        /// </summary>
        private VariablesDictionary publishedVariables = new VariablesDictionary();

        /// <summary>
        /// The string expression resolver.
        /// </summary>
        private IStringExpressionResolver stringExpressionResolver;

        /// <summary>
        /// The subscriptions.
        /// </summary>
        private Subscriptions subscriptions = new Subscriptions();

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="DynamicVariableResolver"/> class.
        /// </summary>
        /// <param name="stringExpressionResolver">
        /// The instance of the object that will resolve string expressions.
        /// </param>
        public DynamicVariableResolver(IStringExpressionResolver stringExpressionResolver)
        {
            this.stringExpressionResolver = stringExpressionResolver;
        }

        /// <summary>
        /// Contains the publishing variables and respective values
        /// </summary>
        internal VariablesDictionary PublishedVariables
        {
            get
            {
                return this.publishedVariables;
            }
        }

        #endregion

        /*
        /// <summary>
        /// Registers the subscribing object.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="instance">The instance.</param>
        public void RegisterSubscribingObject(string name, object instance)
        {
            this.subscriptions.RegisterObjectInstance(name, instance);
        }
        */
        #region Public Methods

        /// <summary>
        /// Adds the object publication.
        /// </summary>
        /// <param name="objectName">
        /// Name of the object.
        /// </param>
        /// <param name="publication">
        /// The publication.
        /// </param>
        public void AddObjectPublication(string objectName, ObjectPublication publication)
        {
            this.publications.AddObjectPublication(objectName, publication);
        }

        /// <summary>
        /// Adds the object subscription.
        /// </summary>
        /// <param name="variableName">
        /// Name of the variable.
        /// </param>
        /// <param name="subscription">
        /// The subscription.
        /// </param>
        public void AddObjectSubscription(string variableName, VariableSubscription subscription)
        {
            this.subscriptions.AddObjectSubscription(variableName, subscription);
        }

        /// <summary>
        /// Clears all object instances but leaves their registration
        /// </summary>
        public void ClearObjectInstances()
        {
            this.publications.ClearObjectInstances();
            this.subscriptions.ClearObjectInstances();
        }

        /// <summary>
        /// Registers the object instance.
        /// </summary>
        /// <param name="name">
        /// The name.
        /// </param>
        /// <param name="instance">
        /// The instance.
        /// </param>
        public void RegisterObjectInstance(string name, object instance)
        {
            this.publications.RegisterObjectInstance(name, instance);
            this.subscriptions.RegisterObjectInstance(name, instance);
        }

        /*
        /// <summary>
        /// Registers the publishing object.
        /// </summary>
        /// <param name="objectName">Name of the object.</param>
        /// <param name="instance">The instance.</param>
        public void RegisterPublishingObject(string objectName, object instance)
        {
            this.publications.RegisterObjectInstance(objectName, instance);
        }
        */

        /// <summary>
        /// Resolves all the dynamic variables associated with the specified instance.
        /// </summary>
        /// <param name="instance">
        /// The instance.
        /// </param>
        public void Resolve(object instance)
        {
            ValidationHelper.VerifyObjectArgument(instance, "instance");

            // First update the publications
            foreach (string objectName in this.publications.Objects)
            {
                foreach (ObjectPublication publication in this.publications[objectName])
                {
                    // Get the new value
                    object newValue = this.GetObjectPropertyValue(publication.Instance, publication.PropertyName);
                    publication.PropertyValue = newValue;

                    // Add it to the published variables collection
                    if (this.PublishedVariables.ContainsKey(publication.VariableName))
                    {
                        this.PublishedVariables.Remove(publication.VariableName);
                    }

                    this.PublishedVariables.Add(publication.VariableName, newValue);
                }
            }

            // Now update the subscriptions
            // List<string> subscriptionsToDelete = new List<string>();
            foreach (string variableName in this.subscriptions.Variables)
            {
                foreach (VariableSubscription subscription in this.subscriptions[variableName])
                {
                    if (ReferenceEquals(instance, subscription.Instance))
                    {
                        if (this.SetPropertyValue(
                            subscription.Instance, 
                            subscription.ObjectPropertyName, 
                            variableName, 
                            subscription.PropertyExpression))
                        {
                            // Todo: Remove the sbscription so that the object is not updated twice
                        }
                    }
                }
            }
        }

        #endregion

        #region Helper Methods

        /// <summary>
        /// Gets the object property value.
        /// </summary>
        /// <param name="objectInstance">
        /// The object instance.
        /// </param>
        /// <param name="propertyName">
        /// Name of the property.
        /// </param>
        /// <returns>
        /// The get object property value.
        /// </returns>
        private object GetObjectPropertyValue(object objectInstance, string propertyName)
        {
            if (objectInstance != null)
            {
                var wrapper = new ObjectWrapper(objectInstance);

                return wrapper.GetPropertyValue(propertyName);
            }

            return null;
        }

        /// <summary>
        /// Resolves the string expression if there is an expression resolver associated with this class.
        /// If not it simply returns the unresolved expression.
        /// </summary>
        /// <param name="propertyExpression">
        /// The property expression.
        /// </param>
        /// <returns>
        /// The resolve expression.
        /// </returns>
        private object ResolveExpression(string propertyExpression)
        {

            // If there is a string expression resolver then resolve expression
            if (this.stringExpressionResolver != null)
            {
                //string expression = this.stringExpressionResolver.ResolveExpression(propertyExpression, this.publishedVariables.Convert());
                //expression = this.stringExpressionResolver.ResolveExpression(propertyExpression, this.publishedVariables.Convert());
                return this.stringExpressionResolver.ResolveExpression(propertyExpression, this.PublishedVariables.Convert());

            }




            // no resolver so jut return the expreseion without changes
            return propertyExpression;
        }

        /// <summary>
        /// Sets the property value.
        /// </summary>
        /// <param name="objectInstance">
        /// The object instance.
        /// </param>
        /// <param name="propertyName">
        /// Name of the property.
        /// </param>
        /// <param name="variableName">
        /// Name of the variable.
        /// </param>
        /// <param name="propertyExpression">
        /// The property expression.
        /// </param>
        /// <returns>
        /// The set property value.
        /// </returns>
        private bool SetPropertyValue(
            object objectInstance, string propertyName, string variableName, string propertyExpression)
        {
            ValidationHelper.VerifyStringArgument(propertyName, "propertyName");
            object variableValue = null;

            if (objectInstance != null)
            {
                var wrapper = new ObjectWrapper(objectInstance);
                
                System.Type type = wrapper.GetPropertyType(propertyName);
                if (type == null)
                {
                    return false;
                }

                if (type == typeof(string))
                {
                    // if it is a string then use the ParseString to get the value
                    variableValue = this.ResolveExpression(propertyExpression);
                }
                else
                {
                    variableValue = this.PublishedVariables[variableName];
                }

                wrapper.SetPropertyValue(propertyName, variableValue);

                return true;
            }

            return false;
        }

        #endregion
    }
}